﻿using System;
using System.Collections.Generic;
using System.Threading;

using OCXS;
using OCXS.OCXSServiceLibrary.Utils;
using OCXS.OCXSServiceLibrary.OPCXMLDA10;

namespace OCXS {

	namespace OCXSServiceLibrary {
        
		public partial class OCXSService {

			/// <summary>
            /// <para>
            /// Implements OPC XML DA 1.0 SubscriptionPolledRefresh service. 
            /// </para>
            /// <para>
            /// See OPC XML DA 1.0 specification for more information about SubscriptionPolledRefresh service.
            /// </para>
			/// </summary>
            /// <remarks>
            /// <para>
            ///     <b>Following there's a list of implemented (or not) standard features.</b>
            /// </para>
            /// <para>
            ///     The following <c>SubscriptionPolledRefreshRequest</c> standard features are (or not) implemented.
            /// 
            ///     <ul>
            ///         <li><b>HoldTime: </b> supported.</li>
            ///         
            ///         <li>
            ///             <b>Options: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>LocaleID: </b> supported.</li>
            ///                 <li><b>RequestDeadline: </b> not supported.</li>
            ///                 <li><b>RequestDeadlineSpecified: </b> ignored.</li>
            ///                 <li><b>ReturnDiagnosticInfo: </b> supported.</li>
            ///                 <li><b>ReturnErrorText: </b> supported.</li>
            ///                 <li><b>ReturnItemName: </b> supported.</li>
            ///                 <li><b>ReturnItemPath: </b> supported.</li>
            ///                 <li><b>ReturnItemTime: </b> supported.</li>
            ///             </ul>
            ///         </li>
            /// 
            ///         <li><b>ReturnAllItems: </b> supported.</li>
            /// 
            ///         <li><b>ServerSubHandles: </b> partially supported (just one subscription handle is considered, the first specified).</li>
            ///         
            ///         <li><b>WaitTime: </b> supported.</li>
            ///     </ul>
            /// </para>
            /// 
            /// <para>
            ///     The following <c>SubscriptionPolledRefreshResponse</c> standard features are (or not) implemented.
            ///     <ul>
            ///         
            ///         <li><b>DataBufferOverflow: </b> not supported (always returned <c>false</c>).</li>
            /// 
            ///         <li>
            ///             <b>Errors: </b> One error per time is reported. The following errors are supported:
            ///             <ul>
            ///                 <li><c>E_OUTOFMEMORY</c></li>
            ///                 <li><c>E_FAIL</c></li>
            ///                 <li><c>E_NOSUBSCRIPTION</c></li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li><b>InvalidServerSubHandles: </b> supported.</li>
            ///         
            ///         <li>
            ///             <b>RItemList: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li>
            ///                     <b>Items: </b> the following features are (or not) supported:
            ///                     <ul>
            ///                         <li><b>ClientItemHandle: </b> supported.</li>
            ///                         <li><b>DiagnosticInfo: </b> supported.</li>
            ///                         <li><b>ItemName: </b> supported.</li>
            ///                         <li><b>ItemPath: </b> supported.</li>
            ///                         <li><b>Quality: </b> supported (static values: <c>good</c>).</li>
            ///                         <li><b>ResultID: </b> not supported.</li>
            ///                         <li><b>Timestamp: </b> supported.</li>
            ///                         <li><b>TimestampSpecified: </b> supported</li>
            ///                         <li><b>Value: </b> supported.</li>
            ///                         <li><b>ValueTypeQualifier: </b> supported (<c>xs:float</c>).</li>
            ///                     </ul>
            ///                 </li>
            ///                 <li><b>SubscriptionHandle: </b> supported.</li>
            ///             </ul>
            ///         </li>
            /// 
            ///         <li>
            ///             <b>SubscriptionPolledRefreshResult: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>RcvTime: </b> supported (evaluated as first instruction).</li>
            ///                 <li><b>ReplyTime: </b> supported (evaluated as last instruction).</li>
            ///                 <li><b>RevisedLocaleID: </b> supported (<c>en-us</c>).</li>
            ///                 <li><b>ServerState: </b> supported (<c>running</c>).</li>
            ///             </ul>
            ///         </li>    
            ///     </ul>
            /// </para>
            /// 
            /// <code>
            /// // The following code can be found in OCXSClient and 
            /// // shows how to call SubscriptionPolledRefresh service.
            /// class Program {
            ///
            ///     private const string EndpointAddress = "http://localhost/OCXS/OCXS.svc";
            ///     
            ///     static void Main(string[] args) {
            ///     
            ///     // In the response subscribed Item values will be returned.
            ///     // The subscription expires after 10 seconds of inactivity.
            ///     SubscribeResponse sr = Subscribe("en-us", "0x00FF", true, 10000);
            ///     
            ///     // Call SubscriptionPolledRefresh on subscription previously initialized.
            ///     // This is an Advanced Polled Refresh, since we provide a valid Hold Time
            ///     // (10 seconds after actual time).
            ///     SubscriptionPolledRefreshResponse sprr = SubscriptionPolledRefresh(
            ///         "en-us",
            ///         "0x0100",
            ///         new string[1] { sr.ServerSubHandle },
            ///         DateTime.Now + new TimeSpan(0, 0, 10),
            ///         5000,
            ///         false);
            ///         
            ///     // After 10 seconds, as specified in the Subscribe, the subscription 
            ///     // will be cancelled due to timeout.
            /// }
            /// 
            /// public static SubscriptionPolledRefreshResponse SubscriptionPolledRefresh(
            /// 	string localeID,
            /// 	string clientRequestHandle,
            /// 	string[] sids,
            /// 	DateTime holdTime,
            /// 	int waitTime,
            /// 	bool returnAllItems,
            /// 	bool returnItemName = true,
            /// 	bool returnItemPath = true,
            /// 	bool returnItemTime = true,
            /// 	bool returnDiagnosticInfo = false,
            /// 	bool returnErrorText = false,
            /// 	// Not supported features down here
            /// 	bool requestDeadlineSpecified = false)
            /// {
            /// 	// Get the channel.
            /// 	// GetOCXSChannel can be found in OCXSClient.
            /// 	IOCXSService svc = GetOCXSChannel(EndpointAddress);
            /// 
            /// 	// Create request options, for ReadRequest.
            /// 	RequestOptions ro = new RequestOptions();
            /// 	ro.ClientRequestHandle = clientRequestHandle;
            /// 	ro.LocaleID = localeID;
            /// 	ro.RequestDeadline = DateTime.Parse("2001-09-11");
            /// 	ro.RequestDeadlineSpecified = requestDeadlineSpecified;
            /// 	ro.ReturnDiagnosticInfo = returnDiagnosticInfo;
            /// 	ro.ReturnErrorText = returnErrorText;
            /// 	ro.ReturnItemName = returnItemName;
            /// 	ro.ReturnItemPath = returnItemPath;
            /// 	ro.ReturnItemTime = returnItemTime;
            /// 
            /// 	// Even though sids is a string[], please mind that actually
            /// 	// just the 1st element (sids[0]) will be considered during
            /// 	// the SubscriptionPolledRefresh call.
            /// 	SubscriptionPolledRefreshRequest SPRR = 
            /// 		new SubscriptionPolledRefreshRequest(
            /// 			ro,
            /// 			sids,
            /// 			holdTime,
            /// 			waitTime,
            /// 			returnAllItems);
            /// 
            ///     // Perform the call to service and get the response.
            /// 	SubscriptionPolledRefreshResponse res = svc.SubscriptionPolledRefresh(SPRR);
            /// 
            ///     // Print the response.
            ///     // PrintResponse can be found in OCXSClient.
            /// 	PrintResponse(res);
            /// 
            /// 	return res;
            /// }
            /// </code>
            /// 
            /// </remarks>
            /// <param name="request">The <c>SubscriptionPolledRefreshRequest</c> issued by client according to OCP XML DA 1.0 specification.</param>
            /// <returns>A <c>SubscriptionPolledRefreshResponse</c> according to OPC XML DA 1.0 specification.</returns>
			public SubscriptionPolledRefreshResponse SubscriptionPolledRefresh(SubscriptionPolledRefreshRequest request) {

                DateTime serviceTimestamp = DateTime.Now; /* Managing timer reset */

                SubscriptionPolledRefreshResponse SPRR = null;
                ReplyBase RB = this.GetReplyBase(request.Options.ClientRequestHandle, request.Options.LocaleID); /* Get RB */

                try {

                    List<SubscriptionRecord> validRecords = new List<SubscriptionRecord>();
                    List<String> invalidSIDs = new List<string>();

                    // Manage record request and fill invalid SID array.
                    // Here we check all subscription but further we will consider only
                    // 0-indexed subscription.
                    foreach (string sid in request.ServerSubHandles) {
                        SubscriptionRecord sr = null;
                        if (this.TryGetSubscriptionRecordBySID(sid, out sr))
                            validRecords.Add(sr);
                        else
                            invalidSIDs.Add(sid);
                    }

                    // Checking validity
                    if (validRecords.Count > 0) {

                        // We will focus on the following subscription.
                        SubscriptionRecord subscription = validRecords[0];

                        // Stop PingElapse timer. It will be restarted before returning.
                        subscription.StopRelease();

                        // *** BEGIN ADVANCED POLLING ***
                        // Managing advanced polled refresh
                        if (request.HoldTime > serviceTimestamp) { /* If not specified => skip on basic */
                            EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset, subscription.SID);

                            // Sleep for HoldTime
                            Thread.Sleep(request.HoldTime - serviceTimestamp);

                            // Entering WaitTime
                            ewh.WaitOne(request.WaitTime);
                            ewh.Reset(); /* Resetting synchronization wait handle */
                        }
                        // *** END ADVANCED POLLING ***

                        List<ItemValue> items = new List<ItemValue>();

                        // Get values and, then, check
                        PolledRefreshVariableLCV[] lcvs = subscription.GetLCVs();
                        // Checking
                        foreach (PolledRefreshVariableLCV lcv in lcvs) {
                            ItemValue iv = new ItemValue();
                            iv.ClientItemHandle = request.Options.ClientRequestHandle;
                            iv.DiagnosticInfo = (request.Options.ReturnDiagnosticInfo) ? "Diagnostics ok! Hurray!" : null;
                            iv.ItemName = (request.Options.ReturnItemName) ? lcv.Name : null;
                            iv.ItemPath = (request.Options.ReturnItemPath) ? lcv.Path : null;

                            iv.Quality = new OPCQuality();
                            iv.Quality.QualityField = qualityBits.good;
                            iv.Quality.LimitField = limitBits.none;
                            iv.Quality.VendorField = 0;

                            iv.ResultID = null;
                            iv.Timestamp = (request.Options.ReturnItemTime) ? DateTime.Now : DateTime.MinValue;
                            iv.TimestampSpecified = request.Options.ReturnItemTime;
                            iv.Value = lcv.Value;
                            iv.ValueTypeQualifier = new System.Xml.XmlQualifiedName("float", "xs");

                            if (request.ReturnAllItems || lcv.Changed)
                                items.Add(iv);
                        }

                        SubscribePolledRefreshReplyItemList SPRRIL = null;
                        if (items.Count > 0) {
                            SPRRIL = new SubscribePolledRefreshReplyItemList();
                            SPRRIL.SubscriptionHandle = subscription.SID;
                            SPRRIL.Items = items.ToArray();
                        }

                        // Ready to return
                        SPRR =
                            new SubscriptionPolledRefreshResponse(RB,
                                invalidSIDs.ToArray(),
                                (SPRRIL == null) ?
                                    null :
                                    new SubscribePolledRefreshReplyItemList[] { SPRRIL },
                                null,
                                false);

                        // Start PingElapse timer.
                        subscription.StartRelease();

                    } else {
                        // Return some error of some kind
                        OPCError opce = new OPCError();
                        opce.ID = new System.Xml.XmlQualifiedName("E_NOSUBSCRIPTION", "http://opcfoundation.org/webservices/XMLDA/1.0/");
                        opce.Text = "Provided subscription does not exist.";

                        SPRR =
                            new SubscriptionPolledRefreshResponse(RB,
                                invalidSIDs.ToArray(),
                                null,
                                new OPCError[] { opce },
                                false);

                        // NOTE: No PingElapse timer start because it wasn't stopped at all. 
                        //       Subscription was not found.
                    }

                    /* Before returning */
                    RB.ReplyTime = DateTime.Now;

                    return SPRR;

                } catch (OutOfMemoryException e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new SubscriptionPolledRefreshResponse(
                            RB,
                            null,
                            null,
                            new OPCError[] { this.BuildOPCError("E_OUTOFMEMORY", e.Message) },
                            false);

                } catch (Exception e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new SubscriptionPolledRefreshResponse(
                            RB,
                            null,
                            null,
                            new OPCError[] { this.BuildOPCError("E_FAIL", e.Message) },
                            false);
                }

            } /* SubscriptionPolledRefresh */

		}
        
	}

}
